#include "src/ltlexer.h"
#include <stdlib.h>
#include <string.h>
#include "src/ltbase.h"
#include "src/lterr.h"
#include "src/ltlog.h"
#include "src/ltt.h"
#include "src/lttry.h"

lt_UsingType(lt, USize);
lt_UsingType(lt, I32);

static bool
IsWs(lt_I32 rune) {
    switch (rune) {
        case ' ':
        case '\n':
        case '\t':
        case '\r': return true;
        default: return false;
    }
}

static void
SkipWs(ltlexer_Lexer* lexer) {
    for (;;) {
        lt_I32 c = lexer->content[lexer->pos];
        if (c == 0 || !IsWs(c))
            break;
        ++lexer->pos;
    }
}

static bool
IsOperatorChar(lt_I32 rune) {
    switch (rune) {
        case ':':
        case '!':
        case '=':
        case '.':
        case '#':
        case ',':
        case ';':

        case '+':
        case '-':
        case '*':
        case '/':

        case '(':
        case ')':
        case '[':
        case ']':
        case '{':
        case '}':
        case '<':
        case '>': return true;
        default: return false;
    }
}

static lterr_Code
ltlexer_NextStringLiteral(ltlexer_Lexer* lexer, ltlexer_Token* token) {
    USize pos = lexer->pos;
    USize len = 0;

    I32 c = lexer->content[pos + len];

    if (c != '"')
        return lterr_kError;
    len += 1;

    bool escape = false;
    for (;; len += 1) {
        c = lexer->content[pos + len];

        if (c == 0) {
            ltlog_Log0(Error, "reach input end before string literal close");
            return lterr_kError;
        }

        if (escape) {
            escape = false;
            continue;
        }

        if (c == '\\') {
            escape = true;
            continue;
        }

        if (c == '"') {
            len += 1;
            break;
        }
    }

    token->type = ltlexer_kStringLiteral;
    token->pos = pos;
    token->len = len;

    return lterr_kOk;
}

static lterr_Code
ltlexer_NextSymbol(ltlexer_Lexer* lexer, ltlexer_Token* token) {
    USize pos = lexer->pos;
    USize len = 0;

    for (I32 c; (c = lexer->content[pos + len]) == '_' || ('0' <= c && c <= '9')
                || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z');
         ++len) {}

    if (len == 0) {
        ltlog_Log0(Error, "empty symbol");
        return lterr_kError;
    }

    token->type = ltlexer_kSymbol;
    token->pos = pos;
    token->len = len;

    return lterr_kOk;
}

static lterr_Code
ltlexer_NextOperator(ltlexer_Lexer* lexer, ltlexer_Token* token) {
    USize pos = lexer->pos;
    USize len = 0;

    while (IsOperatorChar(lexer->content[pos + len])) {
        len += 1;
    }

    token->pos = pos;
    token->len = len;
    token->type = ltlexer_kOperator;

    return lterr_kOk;
}

static lterr_Code
ExpectToken(ltlexer_Lexer* lexer, ltlexer_TokenType type, char const* value) {
    USize len = strlen(value);

    ltlexer_Token token;
    lttry_Try(ltlexer_Next(lexer, &token));

    if (token.type != type) {
        ltlog_Log2(Error, "bad token type", "expect", U32(type), "result",
                   U32(token.type));
        return lterr_kError;
    }

    if (len != token.len) {
        ltlog_Log2(Error, "bad token length", "expect", USize(len), "result",
                   USize(token.len));
        return lterr_kError;
    }

    if (strncmp(lexer->content + token.pos, value, len) != 0) {
        char const* token_dup = strndup(lexer->content + token.pos, len);

        ltlog_Log2(Error, "bad token value", "expect", String(value), "result",
                   String(token_dup));

        free((void*)token_dup);
        return lterr_kError;
    }

    return lterr_kOk;
}

ltt_Test(ltlexer_Next) {
    ltlexer_Lexer lexer = {.content = " a+b <=  /* */ /* comment block*/"
                                      ">= \"hello\" -> * +\"world\""};

    lttry_Try(ExpectToken(&lexer, ltlexer_kSymbol, "a"));
    lttry_Try(ExpectToken(&lexer, ltlexer_kOperator, "+"));
    lttry_Try(ExpectToken(&lexer, ltlexer_kSymbol, "b"));
    lttry_Try(ExpectToken(&lexer, ltlexer_kOperator, "<="));
    lttry_Try(ExpectToken(&lexer, ltlexer_kOperator, ">="));
    lttry_Try(ExpectToken(&lexer, ltlexer_kStringLiteral, "\"hello\""));
    lttry_Try(ExpectToken(&lexer, ltlexer_kOperator, "->"));
    lttry_Try(ExpectToken(&lexer, ltlexer_kOperator, "*"));
    lttry_Try(ExpectToken(&lexer, ltlexer_kOperator, "+"));
    lttry_Try(ExpectToken(&lexer, ltlexer_kStringLiteral, "\"world\""));
    lttry_Try(ExpectToken(&lexer, ltlexer_kEof, ""));

    /* Eof之后始终是Eof */
    lttry_Try(ExpectToken(&lexer, ltlexer_kEof, ""));

    return lterr_kOk;
}

static lterr_Code
SkipCommentBlock(ltlexer_Lexer* lexer) {
    I32 c0, c1;

    for (;; ++lexer->pos) {
        if ((c0 = lexer->content[lexer->pos]) == 0
            || (c1 = lexer->content[lexer->pos + 1]) == 0) {
            ltlog_Log1(Error, "reach input end before comment block /* */ end",
                       "pos", USize(lexer->pos));
            return lterr_kError;
        }

        if (c0 == '*' && c1 == '/') {
            lexer->pos += 2;
            break;
        }
    }

    return lterr_kOk;
}

static lterr_Code
ltlexer_TryNext(ltlexer_Lexer* lexer, ltlexer_Token* token) {
    SkipWs(lexer);

    I32 c = lexer->content[lexer->pos];

    if (c == 0) {
        token->type = ltlexer_kEof;
        token->pos = lexer->pos;
        token->len = 0;
        return lterr_kOk;
    }

    while (c == '/' && lexer->content[lexer->pos + 1] == '*') {
        lttry_Try(SkipCommentBlock(lexer));
        SkipWs(lexer);
        c = lexer->content[lexer->pos];
    }

    if (IsOperatorChar(c))
        return ltlexer_NextOperator(lexer, token);

    if (c == '"')
        return ltlexer_NextStringLiteral(lexer, token);

    if (c == '_' || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'))
        return ltlexer_NextSymbol(lexer, token);

    ltlog_Log2(Error, "invalid leading char for token", "leading", Char(c),
               "pos", USize(lexer->pos));

    return lterr_kError;
}

lterr_Code
ltlexer_Peek(ltlexer_Lexer* lexer, ltlexer_Token* token) {
    USize pos = lexer->pos;

    lterr_Code code = ltlexer_Next(lexer, token);

    token->pos = pos;

    return code;
}

lterr_Code
ltlexer_Next(ltlexer_Lexer* lexer, ltlexer_Token* token) {
    lterr_Code code = ltlexer_TryNext(lexer, token);

    if (code == lterr_kOk) {
        lexer->pos = token->pos + token->len;
    }

    return code;
}
